﻿using System;
using System.IO;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.Xml;
using System.Text.RegularExpressions;
using System.Net;

namespace RSSReader
{
    public delegate void RefreshChannelDelegate(TreeNode node, RssChannel item, List<RssItem> newItems);

    public partial class RSSReader : Form
    {        

        #region <<< [ Variables    ] >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>


        private WebBrowser _webBrowser;
        private NotifyIcon _readerNotifier;
        private StatusBar _statusBar;
        private frmPostAlarm _frmUpdatePosts;
        private Timer _updateTimer;
        private TreeView _treeBlogPost;
        private List<RssChannel> _channels = new List<RssChannel>();

        private string _xmlFileName = "list.xml";

        MenuItem _mnuNewPost = new MenuItem("New Post : 0");

        #endregion

        #region <<< [ Constructors ] >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

        public RSSReader()
        {
            InitializeComponent();
            _channels = new List<RssChannel>();
            _readerNotifier.ContextMenu = new ContextMenu();
            _readerNotifier.ContextMenu.MenuItems.Add(_mnuNewPost);
            _mnuNewPost.Click += new EventHandler(_mnuNewPost_Click);
            _mnuNewPost.Enabled = false;
            _updateTimer.Interval = 180000;
            _updateTimer.Start();
            LoadConfig();
        }        

        #endregion

        #region <<< [ Methods      ] >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

        private void LoadConfig()
        {
            XmlTextReader fileReader = null;
            string sName = "", sUpdateTime = "180000";
            _channels.Clear();

            try
            {
                fileReader = new XmlTextReader(_xmlFileName);
                fileReader.WhitespaceHandling = WhitespaceHandling.None;

                RSSFunctions.MoveAndRead(fileReader, ReadType.Following, "name", ref sName);
                RSSFunctions.MoveAndRead(fileReader, ReadType.Following, "reloadTime", ref sUpdateTime);

                if (int.Parse(sUpdateTime) != _updateTimer.Interval)
                    _updateTimer.Interval = int.Parse(sUpdateTime);

                while (fileReader.ReadToFollowing("url"))
                {
                    string sTitle = "", sLink = "", sLsatpost = "", sDescription = "";

                    RSSFunctions.MoveAndRead(fileReader, ReadType.Descendant, "title", ref sTitle);
                    RSSFunctions.MoveAndRead(fileReader, ReadType.NextSibling, "link", ref sLink);
                    RSSFunctions.MoveAndRead(fileReader, ReadType.NextSibling, "description", ref sDescription);
                    RSSFunctions.MoveAndRead(fileReader, ReadType.NextSibling, "lastPost", ref sLsatpost);

                    _channels.Add(new RssChannel(sTitle, sLink, sDescription, sLsatpost));
                }
            }
            catch (FileNotFoundException)
            {
                fileReader.Close();
            }
        }

        private void InitChannels()
        {
            XmlTextReader blogReader = null;
            _treeBlogPost.Nodes.Clear();            

            foreach (RssChannel channel in _channels)
            {
                try
                {
                    blogReader = new XmlTextReader(channel.Link);
                    blogReader.WhitespaceHandling = WhitespaceHandling.None;

                    if (blogReader.ReadToFollowing("title"))
                    {
                        TreeNode blogNode = new TreeNode(blogReader.ReadString(), 0, 0);
                        blogNode.ImageIndex = 0;
                        blogNode.Nodes.Add(new TreeNode(""));   //빈 자식 노드를 추가해 줍니다. (+표시)
                        channel.Title = blogNode.Text;
                        _treeBlogPost.Nodes.Add(blogNode);
                    }
                }
                catch(Exception)
                {
                    TreeNode blogNode = new TreeNode(channel.Link, 0, 0);
                    blogNode.StateImageIndex = 3;
                    blogNode.ForeColor = Color.Red;
                    _treeBlogPost.Nodes.Add(blogNode);
                }

                blogReader.Close();
            }            
        }

        private void SaveConfig()
        {
            XmlTextWriter configWriter = null;
            try
            {
                configWriter = new XmlTextWriter("config.xml", Encoding.UTF8);
                configWriter.Formatting = Formatting.Indented;
                configWriter.Indentation = 4;

                //list.xml 파일
                configWriter.WriteStartDocument();
                configWriter.WriteStartElement("config");
                configWriter.WriteElementString("CurrentDataFile", _xmlFileName);
                configWriter.WriteElementString("Width", this.Width.ToString());
                configWriter.WriteElementString("Height", this.Height.ToString());
                configWriter.WriteEndElement();
                configWriter.WriteEndDocument();

                configWriter.Close();
            }
            catch (Exception ex)
            {
                if (configWriter != null)
                    configWriter.Close();
                MessageBox.Show(ex.Message, ex.Source, MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
            
        }
                
        private void UpdateChannelItems(TreeNode node, RssChannel item, List<RssItem> newItems)
        {
            RSSFunctions.ReadItems(node, _channels[node.Index]);
            
            node.ForeColor = Color.Black;

            if (item.LastPost == "")
            {
                foreach (TreeNode subNode in node.Nodes)
                {
                    newItems.Add(new RssChannel(subNode.Text, subNode.Name.ToString(), (string)subNode.Tag));
                    subNode.ForeColor = Color.Green;
                    node.ForeColor = Color.Green;
                }

                if (node.Nodes.Count >= 1 && node.Nodes[0].Text != "")
                    item.LastPost = node.Nodes[0].Name;
            }
            else
            {
                foreach (TreeNode subNode in node.Nodes)
                {
                    if (!(item.LastPost.Equals(subNode.Name.ToString())))
                    {
                        newItems.Add(new RssItem(subNode.Text, subNode.Name.ToString(), (string)subNode.Tag));
                        subNode.ForeColor = Color.Green;
                        node.ForeColor = Color.Green;
                    }
                    else
                        break;
                }

                if (node.Nodes.Count >= 1 && node.Nodes[0].Text != "")
                    item.LastPost = node.Nodes[0].Name;
            }
        }

        private void UpdateChannels()
        {
            List<RssItem> newItems = new List<RssItem>();

            foreach (TreeNode node in _treeBlogPost.Nodes)
            {
                node.Nodes.Clear();
                UpdateChannelItems(node, _channels[node.Index], newItems);
            }

            if (newItems.Count != 0)
            {
                _frmUpdatePosts = new frmPostAlarm(newItems);

                _mnuNewPost.Text = "New Posts : " + newItems.Count.ToString();
                _mnuNewPost.Enabled = true;

                _readerNotifier.BalloonTipTitle = "RSS Alarm";
                _readerNotifier.BalloonTipText = "New Posts : " + newItems.Count.ToString();
                _readerNotifier.ShowBalloonTip(10000);
            }
        }

        protected override void Dispose(bool disposing)
        {
            this.Visible = false;
        }

        #endregion

        #region <<< [ Events       ] >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

        /// <summary>
        /// Form Load 이벤트
        /// </summary>
        /// <param name="sender">이벤트를 호출한 Object</param>
        /// <param name="e">EventArgs</param>
        private void frmRSSReader_Load(object sender, System.EventArgs e)
        {
            // 
            // ImageList
            // 
            this._treeBlogPost.ImageList = new ImageList();
            this._treeBlogPost.ImageList.ColorDepth = System.Windows.Forms.ColorDepth.Depth8Bit;
            this._treeBlogPost.ImageList.ImageSize = new System.Drawing.Size(16, 16);
            this._treeBlogPost.ImageList.TransparentColor = System.Drawing.Color.Transparent;
            this._treeBlogPost.ImageList.Images.Add(global::RSSReader.Properties.Resources.channel);
            this._treeBlogPost.ImageList.Images.Add(global::RSSReader.Properties.Resources.item);            
            this._treeBlogPost.ImageList.Images.Add(global::RSSReader.Properties.Resources.item_read);
            this._treeBlogPost.ImageList.Images.Add(global::RSSReader.Properties.Resources.error);

            LoadConfig();
            InitChannels();
        }

        /// <summary>
        /// 노드가 확장되기 전에 이벤트
        /// </summary>
        /// <param name="sender">이벤트를 호출한 Object</param>
        /// <param name="e">TreeViewCancelEventArgs</param>
        private void _treeBlogPost_BeforeExpand(object sender, System.Windows.Forms.TreeViewCancelEventArgs e)
        {
            bool isChannelNode = (e.Node.Parent == null);
            bool isExpanded = (e.Node.Nodes.Count > 1);

            if (isChannelNode && !isExpanded)
                RSSFunctions.ReadItems(e.Node, _channels[e.Node.Index]);                
        }
       
        /// <summary>
        /// 선택항목이 변경된 후에 발생하는 이벤트 
        /// </summary>
        /// <param name="sender">이벤트를 호출한 Object</param>
        /// <param name="e">TreeViewEventArgs</param>
        private void _treeBlogPost_AfterSelect(object sender, System.Windows.Forms.TreeViewEventArgs e)
        {
            bool isItemNode = (e.Node.Parent != null);
            bool HasLink = (e.Node.Name != "");

            try
            {
                //title노드가 있다면 포스트를 가져옵니다.
                if (isItemNode && HasLink)
                {
                    _statusBar.Text = e.Node.Name;
                    string contents = "<style> <!-- body { margin: 10; padding: 0; font-size: 12px; line-height: 1.5em; } IMG { BORDER : 0; } A:LINK { TEXT-DECORATION: NONE; BORDER : 0; } A:VISITED { TEXT-DECORATION: NONE; BORDER : 0; }--> </style>";
                    contents += "<b><h2><a href=" + e.Node.Name + ">" + e.Node.Text + "</a></h2></b><hr>";
                    contents += e.Node.Tag.ToString();
                    _webBrowser.DocumentText = contents;                    
                }
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message, ex.Source, MessageBoxButtons.OK);
            }
        }

        private void _treeBlogPost_KeyDown(object sender, KeyEventArgs e)
        {
            if (e.KeyCode == Keys.Delete)
            {
                //트리뷰에서 선택한 노드에 url이 있다면, 다시말해 블로그가 아닌 포스트를 선택하고 있으면입니다.
                if (_treeBlogPost.Nodes.Count == 0 || _treeBlogPost.SelectedNode.Name == null)
                {
                    MessageBox.Show("삭제할 블로그를 선택하세요!", "블로그 삭제 애러", MessageBoxButtons.OK, MessageBoxIcon.Hand);
                    return;
                }

                if (MessageBox.Show("정말 삭제 하시겠습니까?", "블로그삭제", MessageBoxButtons.YesNo, MessageBoxIcon.Question) == DialogResult.Yes)
                {
                    //배열에서 삭제합니다.
                    _channels.RemoveAt(_treeBlogPost.SelectedNode.Index);

                    //선택된 블로그를 트리뷰에서 삭제합니다.
                    _treeBlogPost.Nodes.Remove(_treeBlogPost.SelectedNode);

                    RSSFunctions.WriteDataXml(_channels, _xmlFileName, _updateTimer.Interval);
                }
            }
        }

        private void _treeBlogPost_DoubleClick(object sender, EventArgs e)
        {
            bool isChannelNode = (_treeBlogPost.SelectedNode.Parent == null);
            bool isExpanded = _treeBlogPost.SelectedNode.IsExpanded;

            if (isChannelNode && !isExpanded)
                RSSFunctions.ReadItems(_treeBlogPost.SelectedNode, _channels[ _treeBlogPost.SelectedNode.Index]);
        }

        private void _mnuNewPost_Click(object sender, EventArgs e)
        {
            _frmUpdatePosts.ShowDialog();
            _frmUpdatePosts.Focus();
        }

        private void tsmOpen_Click(object sender, EventArgs e)
        {
            OpenFileDialog openFileDialog = new OpenFileDialog();
            openFileDialog.InitialDirectory = Application.StartupPath;
            openFileDialog.Multiselect = false;
            openFileDialog.Filter = "XML | *.xml";

            if (openFileDialog.ShowDialog() == DialogResult.OK)
            {
                _xmlFileName = openFileDialog.FileName;
                LoadConfig();
                InitChannels();
            }
        }

        private void tsmSaveAs_Click(object sender, EventArgs e)
        {
            SaveFileDialog saveFileDialog = new SaveFileDialog();
            saveFileDialog.InitialDirectory = Application.StartupPath;
            saveFileDialog.Filter = "XML | *.xml";

            if (saveFileDialog.ShowDialog() == DialogResult.OK)
            {
                _xmlFileName = saveFileDialog.FileName;
                RSSFunctions.WriteDataXml(_channels, _xmlFileName, _updateTimer.Interval);
            }
        }

        private void tsmSave_Click(object sender, EventArgs e)
        {
            if (!RSSFunctions.WriteDataXml(_channels, _xmlFileName, _updateTimer.Interval))
                MessageBox.Show("저장하는 과정에서 오류가 발생했습니다.", "블로그 저장 오류", MessageBoxButtons.OK, MessageBoxIcon.Error);
            else
                MessageBox.Show("[ " + _xmlFileName + " ] 에 저장되었습니다.", "저장완료", MessageBoxButtons.OK, MessageBoxIcon.Information);
        }

        private void tsmExit_Click(object sender, EventArgs e)
        {
            if (MessageBox.Show("종료 하시겠습니까?", "RSS Reader 종료", MessageBoxButtons.YesNo, MessageBoxIcon.Question) == DialogResult.Yes)
            {
                _updateTimer.Stop();
                SaveConfig();

                if (components != null)
                    components.Dispose();

                base.Dispose(true);
            }
        }

        private void tsmSetupRSS_Click(object sender, EventArgs e)
        {
            frmSetupUrl setup = new frmSetupUrl();

            if (setup.SetupUrl(ref _channels, _xmlFileName, _updateTimer.Interval) == DialogResult.OK)
                InitChannels();
        }

        private void tsmSetupTimer_Click(object sender, EventArgs e)
        {
            frmSetupTimer setup = new frmSetupTimer();
            setup.SetupTimer(ref _channels, ref _updateTimer, _xmlFileName);
        }

        private void tsmRefresh_Click(object sender, EventArgs e)
        {
            UpdateChannels();
        }

        private void newPostTimer_Tick(object sender, EventArgs e)
        {
            UpdateChannels();
        }                

        private void readerNotifier_BalloonTipClicked(object sender, EventArgs e)
        {
            _frmUpdatePosts.ShowDialog();
            _frmUpdatePosts.BringToFront();
            _frmUpdatePosts.Focus();
        }        

        private void readerNotifier_DoubleClick(object sender, EventArgs e)
        {
            this.Visible = true;
            this.BringToFront();
            this.Focus();
        }        

        #endregion
    }
}